/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Depth buffer performance tests.
 *//*--------------------------------------------------------------------*/

#include "es3pDepthTests.hpp"

#include "glsCalibration.hpp"

#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuCPUWarmup.hpp"
#include "tcuCommandLine.hpp"
#include "tcuResultCollector.hpp"

#include "deClock.h"
#include "deString.h"
#include "deMath.h"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deUniquePtr.hpp"

#include <vector>
#include <algorithm>

namespace deqp
{
namespace gles3
{
namespace Performance
{
namespace
{
using namespace glw;
using de::MovePtr;
using tcu::TestContext;
using tcu::TestLog;
using tcu::Vec4;
using tcu::Vec3;
using tcu::Vec2;
using glu::RenderContext;
using glu::ProgramSources;
using glu::ShaderSource;
using std::vector;
using std::string;
using std::map;

struct Sample
{
	deInt64	nullTime;
	deInt64	baseTime;
	deInt64	testTime;
	int		order;
	int		workload;
};

struct SampleParams
{
	int step;
	int measurement;

	SampleParams(int step_, int measurement_) : step(step_), measurement(measurement_) {}
};

typedef vector<float> Geometry;

struct ObjectData
{
	ProgramSources	shader;
	Geometry		geometry;

	ObjectData (const ProgramSources& shader_, const Geometry& geometry_) : shader(shader_), geometry(geometry_) {}
};

class RenderData
{
public:
								RenderData		(const ObjectData& object, const glu::RenderContext& renderCtx, TestLog& log);
								~RenderData		(void) {}

	const glu::ShaderProgram	m_program;
	const glu::VertexArray		m_vao;
	const glu::Buffer			m_vbo;

	const int					m_numVertices;
};

RenderData::RenderData (const ObjectData& object, const  glu::RenderContext& renderCtx, TestLog& log)
	: m_program		(renderCtx, object.shader)
	, m_vao			(renderCtx.getFunctions())
	, m_vbo			(renderCtx.getFunctions())
	, m_numVertices	(int(object.geometry.size())/4)
{
	const glw::Functions& gl = renderCtx.getFunctions();

	if (!m_program.isOk())
		log << m_program;

	gl.bindBuffer(GL_ARRAY_BUFFER, *m_vbo);
	gl.bufferData(GL_ARRAY_BUFFER, object.geometry.size() * sizeof(float), &object.geometry[0], GL_STATIC_DRAW);
	gl.bindAttribLocation(m_program.getProgram(), 0, "a_position");

	gl.bindVertexArray(*m_vao);
	gl.vertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray(0);
	gl.bindVertexArray(0);
}

namespace Utils
{
	vector<float> getFullscreenQuad (float depth)
	{
		const float data[] =
		{
			+1.0f, +1.0f, depth, 0.0f, // .w is gl_VertexId%3 since Nexus 4&5 can't handle that on their own
			+1.0f, -1.0f, depth, 1.0f,
			-1.0f, -1.0f, depth, 2.0f,
			-1.0f, -1.0f, depth, 0.0f,
			-1.0f, +1.0f, depth, 1.0f,
			+1.0f, +1.0f, depth, 2.0f,
		};

		return vector<float>(DE_ARRAY_BEGIN(data), DE_ARRAY_END(data));
	}

	vector<float> getFullscreenQuadWithGradient (float depth0, float depth1)
	{
		const float data[] =
		{
			+1.0f, +1.0f, depth0, 0.0f,
			+1.0f, -1.0f, depth0, 1.0f,
			-1.0f, -1.0f, depth1, 2.0f,
			-1.0f, -1.0f, depth1, 0.0f,
			-1.0f, +1.0f, depth1, 1.0f,
			+1.0f, +1.0f, depth0, 2.0f,
		};

		return vector<float>(DE_ARRAY_BEGIN(data), DE_ARRAY_END(data));
	}

	vector<float> getPartScreenQuad (float coverage, float depth)
	{
		const float xMax	= -1.0f + 2.0f*coverage;
		const float data[]	=
		{
			 xMax, +1.0f, depth, 0.0f,
			 xMax, -1.0f, depth, 1.0f,
			-1.0f, -1.0f, depth, 2.0f,
			-1.0f, -1.0f, depth, 0.0f,
			-1.0f, +1.0f, depth, 1.0f,
			 xMax, +1.0f, depth, 2.0f,
		};

		return vector<float>(DE_ARRAY_BEGIN(data), DE_ARRAY_END(data));
	}

	// Axis aligned grid. Depth of vertices is baseDepth +/- depthNoise
	vector<float> getFullScreenGrid (int resolution, deUint32 seed, float baseDepth, float depthNoise, float xyNoise)
	{
		const int		gridsize	= resolution+1;
		vector<Vec3>	vertices	(gridsize*gridsize);
		vector<float>	retval;
		de::Random		rng			(seed);

		for (int y = 0; y < gridsize; y++)
		for (int x = 0; x < gridsize; x++)
		{
			const bool	isEdge	= x == 0 || y == 0 || x == resolution || y == resolution;
			const float x_		= float(x)/float(resolution)*2.0f - 1.0f + (isEdge ? 0.0f : rng.getFloat(-xyNoise, +xyNoise));
			const float y_		= float(y)/float(resolution)*2.0f - 1.0f + (isEdge ? 0.0f : rng.getFloat(-xyNoise, +xyNoise));
			const float z_		= baseDepth + rng.getFloat(-depthNoise, +depthNoise);

			vertices[y*gridsize + x] = Vec3(x_, y_, z_);
		}

		retval.reserve(resolution*resolution*6);

		for (int y = 0; y < resolution; y++)
		for (int x = 0; x < resolution; x++)
		{
			const Vec3& p0 = vertices[(y+0)*gridsize + (x+0)];
			const Vec3& p1 = vertices[(y+0)*gridsize + (x+1)];
			const Vec3& p2 = vertices[(y+1)*gridsize + (x+0)];
			const Vec3& p3 = vertices[(y+1)*gridsize + (x+1)];

			const float temp[6*4] =
			{
				p0.x(), p0.y(), p0.z(), 0.0f,
				p2.x(), p2.y(), p2.z(), 1.0f,
				p1.x(), p1.y(), p1.z(), 2.0f,

				p3.x(), p3.y(), p3.z(), 0.0f,
				p1.x(), p1.y(), p1.z(), 1.0f,
				p2.x(), p2.y(), p2.z(), 2.0f,
			};

			retval.insert(retval.end(), DE_ARRAY_BEGIN(temp), DE_ARRAY_END(temp));
		}

		return retval;
	}

	// Outputs barycentric coordinates as v_bcoords. Otherwise a passthrough shader
	string getBaseVertexShader (void)
	{
		return "#version 300 es\n"
				"in highp vec4 a_position;\n"
				"out mediump vec3 v_bcoords;\n"
				"void main()\n"
				"{\n"
				"	v_bcoords = vec3(0, 0, 0);\n"
				"	v_bcoords[int(a_position.w)] = 1.0;\n"
				"	gl_Position = vec4(a_position.xyz, 1.0);\n"
				"}\n";
	}

	// Adds noise to coordinates based on InstanceID Outputs barycentric coordinates as v_bcoords
	string getInstanceNoiseVertexShader (void)
	{
		return "#version 300 es\n"
				"in highp vec4 a_position;\n"
				"out mediump vec3 v_bcoords;\n"
				"void main()\n"
				"{\n"
				"	v_bcoords = vec3(0, 0, 0);\n"
				"	v_bcoords[int(a_position.w)] = 1.0;\n"
				"	vec3 noise = vec3(sin(float(gl_InstanceID)*1.05), sin(float(gl_InstanceID)*1.23), sin(float(gl_InstanceID)*1.71));\n"
				"	gl_Position = vec4(a_position.xyz + noise * 0.005, 1.0);\n"
				"}\n";
	}

	// Renders green triangles with edges highlighted. Exact shade depends on depth.
	string getDepthAsGreenFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(d,1,d,1);\n"
				"	else\n"
				"		fragColor = vec4(0,d,0,1);\n"
				"}\n";
	}

	// Renders green triangles with edges highlighted. Exact shade depends on depth.
	string getDepthAsRedFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,d,d,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	// Basic time waster. Renders red triangles with edges highlighted. Exact shade depends on depth.
	string getArithmeticWorkloadFragmentShader (void)
	{

		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"uniform mediump int u_iterations;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	for (int i = 0; i<u_iterations; i++)\n"
				// cos(a)^2 + sin(a)^2 == 1. since d is in range [0,1] this will lose a few ULP's of precision per iteration but should not significantly change the value of d without extreme iteration counts
				"		d = d*sin(d)*sin(d) + d*cos(d)*cos(d);\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,d,d,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	// Arithmetic workload shader but contains discard
	string getArithmeticWorkloadDiscardFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"uniform mediump int u_iterations;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	for (int i = 0; i<u_iterations; i++)\n"
				"		d = d*sin(d)*sin(d) + d*cos(d)*cos(d);\n"
				"	if (d < 0.5) discard;\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,d,d,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	// Texture fetch based time waster. Renders red triangles with edges highlighted. Exact shade depends on depth.
	string getTextureWorkloadFragmentShader (void)
	{
		return  "#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"uniform mediump int u_iterations;\n"
				"uniform sampler2D u_texture;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	for (int i = 0; i<u_iterations; i++)\n"
				"		d *= texture(u_texture, (gl_FragCoord.xy+vec2(i))/512.0).r;\n" // Texture is expected to be fully white
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,1,1,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	// Discard fragments in a grid pattern
	string getGridDiscardFragmentShader (int gridsize)
	{
		const string		fragSrc = "#version 300 es\n"
									  "in mediump vec3 v_bcoords;\n"
									  "out mediump vec4 fragColor;\n"
									  "void main()\n"
									  "{\n"
									  "	mediump float d = gl_FragCoord.z;\n"
									  "	if ((int(gl_FragCoord.x)/${GRIDRENDER_SIZE} + int(gl_FragCoord.y)/${GRIDRENDER_SIZE})%2 == 0)\n"
									  "		discard;\n"
									  "	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
									  "		fragColor = vec4(d,1,d,1);\n"
									  "	else\n"
									  "		fragColor = vec4(0,d,0,1);\n"
									  "}\n";
		map<string, string>	params;

		params["GRIDRENDER_SIZE"] = de::toString(gridsize);

		return tcu::StringTemplate(fragSrc).specialize(params);
	}

	// A static increment to frag depth
	string getStaticFragDepthFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	gl_FragDepth = gl_FragCoord.z + 0.1;\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(d,1,d,1);\n"
				"	else\n"
				"		fragColor = vec4(0,d,0,1);\n"
				"}\n";
	}

	// A trivial dynamic change to frag depth
	string getDynamicFragDepthFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	gl_FragDepth = gl_FragCoord.z + (v_bcoords.x + v_bcoords.y + v_bcoords.z)*0.05;\n" // Sum of v_bcoords components is allways 1
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(d,1,d,1);\n"
				"	else\n"
				"		fragColor = vec4(0,d,0,1);\n"
				"}\n";
	}

	// A static increment to frag depth
	string getStaticFragDepthArithmeticWorkloadFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"uniform mediump int u_iterations;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	gl_FragDepth = gl_FragCoord.z + 0.1;\n"
				"	for (int i = 0; i<u_iterations; i++)\n"
				"		d = d*sin(d)*sin(d) + d*cos(d)*cos(d);\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,d,d,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	// A trivial dynamic change to frag depth
	string getDynamicFragDepthArithmeticWorkloadFragmentShader (void)
	{
		return	"#version 300 es\n"
				"in mediump vec3 v_bcoords;\n"
				"out mediump vec4 fragColor;\n"
				"uniform mediump int u_iterations;\n"
				"void main()\n"
				"{\n"
				"	mediump float d = gl_FragCoord.z;\n"
				"	gl_FragDepth = gl_FragCoord.z + (v_bcoords.x + v_bcoords.y + v_bcoords.z)*0.05;\n" // Sum of v_bcoords components is allways 1
				"	for (int i = 0; i<u_iterations; i++)\n"
				"		d = d*sin(d)*sin(d) + d*cos(d)*cos(d);\n"
				"	if (v_bcoords.x < 0.02 || v_bcoords.y < 0.02 || v_bcoords.z < 0.02)\n"
				"		fragColor = vec4(1,d,d,1);\n"
				"	else\n"
				"		fragColor = vec4(d,0,0,1);\n"
				"}\n";
	}

	glu::ProgramSources getBaseShader (void)
	{
		return glu::makeVtxFragSources(getBaseVertexShader(), getDepthAsGreenFragmentShader());
	}

	glu::ProgramSources getArithmeticWorkloadShader (void)
	{
		return glu::makeVtxFragSources(getBaseVertexShader(), getArithmeticWorkloadFragmentShader());
	}

	glu::ProgramSources getArithmeticWorkloadDiscardShader (void)
	{
		return glu::makeVtxFragSources(getBaseVertexShader(), getArithmeticWorkloadDiscardFragmentShader());
	}

	glu::ProgramSources getTextureWorkloadShader (void)
	{
		return glu::makeVtxFragSources(getBaseVertexShader(), getTextureWorkloadFragmentShader());
	}

	glu::ProgramSources getGridDiscardShader (int gridsize)
	{
		return glu::makeVtxFragSources(getBaseVertexShader(), getGridDiscardFragmentShader(gridsize));
	}

	inline ObjectData quadWith (const glu::ProgramSources& shader, float depth)
	{
		return ObjectData(shader, getFullscreenQuad(depth));
	}

	inline ObjectData quadWith (const string& fragShader, float depth)
	{
		return ObjectData(glu::makeVtxFragSources(getBaseVertexShader(), fragShader), getFullscreenQuad(depth));
	}

	inline ObjectData variableQuad (float depth)
	{
		return ObjectData(glu::makeVtxFragSources(getInstanceNoiseVertexShader(), getDepthAsRedFragmentShader()), getFullscreenQuad(depth));
	}

	inline ObjectData fastQuad (float depth)
	{
		return ObjectData(getBaseShader(), getFullscreenQuad(depth));
	}

	inline ObjectData slowQuad (float depth)
	{
		return ObjectData(getArithmeticWorkloadShader(), getFullscreenQuad(depth));
	}

	inline ObjectData fastQuadWithGradient (float depth0, float depth1)
	{
		return ObjectData(getBaseShader(), getFullscreenQuadWithGradient(depth0, depth1));
	}
} // Utils

// Shared base
class BaseCase : public tcu::TestCase
{
public:
	enum {RENDER_SIZE = 512};

							BaseCase			(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc);
	virtual					~BaseCase			(void) {}

	virtual IterateResult	iterate				(void);

protected:
	void					logSamples			(const vector<Sample>& samples, const string& name, const string& desc);
	void					logGeometry			(const tcu::ConstPixelBufferAccess& sample, const glu::ShaderProgram& occluderProg, const glu::ShaderProgram& occludedProg);
	virtual void			logAnalysis			(const vector<Sample>& samples) = 0;
	virtual void			logDescription		(void) = 0;

	virtual ObjectData		genOccluderGeometry	(void) const = 0;
	virtual ObjectData		genOccludedGeometry	(void) const = 0;

	virtual int				calibrate			(void) const = 0;
	virtual Sample			renderSample		(const RenderData& occluder, const RenderData& occluded, int workload) const = 0;

	void					render				(const RenderData& data) const;
	void					render				(const RenderData& data, int instances) const;

	const RenderContext&	m_renderCtx;
	tcu::ResultCollector	m_results;

	enum {ITERATION_STEPS = 10, ITERATION_SAMPLES = 16};
};

BaseCase::BaseCase (TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
	: TestCase		(testCtx, tcu::NODETYPE_PERFORMANCE, name, desc)
	, m_renderCtx	(renderCtx)
{
}

BaseCase::IterateResult BaseCase::iterate (void)
{
	typedef de::MovePtr<RenderData> RenderDataP;

	const glw::Functions&	gl					= m_renderCtx.getFunctions();
	TestLog&				log					= m_testCtx.getLog();

	const glu::Framebuffer	framebuffer			(gl);
	const glu::Renderbuffer	renderbuffer		(gl);
	const glu::Renderbuffer	depthbuffer			(gl);

	vector<Sample>			results;
	vector<int>				params;
	RenderDataP				occluderData;
	RenderDataP				occludedData;
	tcu::TextureLevel		resultTex			(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), RENDER_SIZE, RENDER_SIZE);
	int						maxWorkload			= 0;
	de::Random				rng					(deInt32Hash(deStringHash(getName())) ^ m_testCtx.getCommandLine().getBaseSeed());

	logDescription();

	gl.bindRenderbuffer(GL_RENDERBUFFER, *renderbuffer);
	gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, RENDER_SIZE, RENDER_SIZE);
	gl.bindRenderbuffer(GL_RENDERBUFFER, *depthbuffer);
	gl.renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, RENDER_SIZE, RENDER_SIZE);

	gl.bindFramebuffer(GL_FRAMEBUFFER, *framebuffer);
	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *renderbuffer);
	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *depthbuffer);
	gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
	gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);

	maxWorkload = calibrate();

	// Setup data
	occluderData = RenderDataP(new RenderData (genOccluderGeometry(), m_renderCtx, log));
	occludedData = RenderDataP(new RenderData (genOccludedGeometry(), m_renderCtx, log));

	TCU_CHECK(occluderData->m_program.isOk());
	TCU_CHECK(occludedData->m_program.isOk());

	// Force initialization of GPU resources
	gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	gl.enable(GL_DEPTH_TEST);

	render(*occluderData);
	render(*occludedData);
	glu::readPixels(m_renderCtx, 0, 0, resultTex.getAccess());

	logGeometry(resultTex.getAccess(), occluderData->m_program, occludedData->m_program);

	params.reserve(ITERATION_STEPS*ITERATION_SAMPLES);

	// Setup parameters
	for (int step = 0; step < ITERATION_STEPS; step++)
	{
		const int workload = maxWorkload*step/ITERATION_STEPS;

		for (int count = 0; count < ITERATION_SAMPLES; count++)
			params.push_back(workload);
	}

	rng.shuffle(params.begin(), params.end());

	// Render samples
	for (size_t ndx = 0; ndx < params.size(); ndx++)
	{
		const int	workload	= params[ndx];
		Sample		sample		= renderSample(*occluderData, *occludedData, workload);

		sample.workload = workload;
		sample.order = int(ndx);

		results.push_back(sample);
	}

	logSamples(results, "Samples", "Samples");
	logAnalysis(results);

	m_results.setTestContextResult(m_testCtx);

	return STOP;
}

void BaseCase::logSamples (const vector<Sample>& samples, const string& name, const string& desc)
{
	TestLog& log = m_testCtx.getLog();

	bool testOnly = true;

	for (size_t ndx = 0; ndx < samples.size(); ndx++)
	{
		if (samples[ndx].baseTime != 0 || samples[ndx].nullTime != 0)
		{
			testOnly = false;
			break;
		}
	}

	log << TestLog::SampleList(name, desc);

	if (testOnly)
	{
		log << TestLog::SampleInfo
			<< TestLog::ValueInfo("Workload",	"Workload",			"",				QP_SAMPLE_VALUE_TAG_PREDICTOR)
			<< TestLog::ValueInfo("Order",		"Order of sample",	"",				QP_SAMPLE_VALUE_TAG_PREDICTOR)
			<< TestLog::ValueInfo("TestTime",	"Test render time",	"us",			QP_SAMPLE_VALUE_TAG_RESPONSE)
			<< TestLog::EndSampleInfo;

		for (size_t sampleNdx = 0; sampleNdx < samples.size(); sampleNdx++)
		{
			const Sample& sample = samples[sampleNdx];

			log << TestLog::Sample << sample.workload << sample.order << sample.testTime << TestLog::EndSample;
		}
	}
	else
	{
		log << TestLog::SampleInfo
			<< TestLog::ValueInfo("Workload",	"Workload",			"",				QP_SAMPLE_VALUE_TAG_PREDICTOR)
			<< TestLog::ValueInfo("Order",		"Order of sample",	"",				QP_SAMPLE_VALUE_TAG_PREDICTOR)
			<< TestLog::ValueInfo("TestTime",	"Test render time",	"us",			QP_SAMPLE_VALUE_TAG_RESPONSE)
			<< TestLog::ValueInfo("NullTime",	"Read pixels time",	"us",			QP_SAMPLE_VALUE_TAG_RESPONSE)
			<< TestLog::ValueInfo("BaseTime",	"Base render time",	"us",			QP_SAMPLE_VALUE_TAG_RESPONSE)
			<< TestLog::EndSampleInfo;

		for (size_t sampleNdx = 0; sampleNdx < samples.size(); sampleNdx++)
		{
			const Sample& sample = samples[sampleNdx];

			log << TestLog::Sample << sample.workload << sample.order << sample.testTime << sample.nullTime << sample.baseTime << TestLog::EndSample;
		}
	}

	log << TestLog::EndSampleList;
}

void BaseCase::logGeometry (const tcu::ConstPixelBufferAccess& sample, const glu::ShaderProgram& occluderProg, const glu::ShaderProgram& occludedProg)
{
	TestLog& log = m_testCtx.getLog();

	log << TestLog::Section("Geometry", "Geometry");
	log << TestLog::Message << "Occluding geometry is green with shade dependent on depth (rgb == 0, depth, 0)" << TestLog::EndMessage;
	log << TestLog::Message << "Occluded geometry is red with shade dependent on depth (rgb == depth, 0, 0)" << TestLog::EndMessage;
	log << TestLog::Message << "Primitive edges are a lighter shade of red/green" << TestLog::EndMessage;

	log << TestLog::Image("Test Geometry", "Test Geometry",  sample);
	log << TestLog::EndSection;

	log << TestLog::Section("Occluder", "Occluder");
	log << occluderProg;
	log << TestLog::EndSection;

	log << TestLog::Section("Occluded", "Occluded");
	log << occludedProg;
	log << TestLog::EndSection;
}

void BaseCase::render (const RenderData& data) const
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	gl.useProgram(data.m_program.getProgram());

	gl.bindVertexArray(*data.m_vao);
	gl.drawArrays(GL_TRIANGLES, 0, data.m_numVertices);
	gl.bindVertexArray(0);
}

void BaseCase::render (const RenderData& data, int instances) const
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	gl.useProgram(data.m_program.getProgram());

	gl.bindVertexArray(*data.m_vao);
	gl.drawArraysInstanced(GL_TRIANGLES, 0, data.m_numVertices, instances);
	gl.bindVertexArray(0);
}

// Render occluder once, then repeatedly render occluded geometry. Sample with multiple repetition counts & establish time per call with linear regression
class RenderCountCase : public BaseCase
{
public:
					RenderCountCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc);
					~RenderCountCase	(void) {}

protected:
	virtual void	logAnalysis			(const vector<Sample>& samples);

private:
	virtual int		calibrate			(void) const;
	virtual Sample	renderSample		(const RenderData& occluder, const RenderData& occluded, int callcount) const;
};

RenderCountCase::RenderCountCase (TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
	: BaseCase	(testCtx, renderCtx, name, desc)
{
}

void RenderCountCase::logAnalysis (const vector<Sample>& samples)
{
	using namespace gls;

	TestLog&		log			= m_testCtx.getLog();
	int				maxWorkload	= 0;
	vector<Vec2>	testSamples	(samples.size());

	for (size_t ndx = 0; ndx < samples.size(); ndx++)
	{
		const Sample& sample = samples[ndx];

		testSamples[ndx] = Vec2((float)sample.workload, (float)sample.testTime);

		maxWorkload = de::max(maxWorkload, sample.workload);
	}

	{
		const float							confidence	= 0.60f;
		const LineParametersWithConfidence	testParam	= theilSenSiegelLinearRegression(testSamples, confidence);
		const float							usPerCall	= testParam.coefficient;
		const float							pxPerCall	= RENDER_SIZE*RENDER_SIZE;
		const float							pxPerUs		= pxPerCall/usPerCall;
		const float							mpxPerS		= pxPerUs;

		log << TestLog::Section("Linear Regression", "Linear Regression");
		log << TestLog::Message << "Offset & coefficient presented as [confidence interval min, estimate, confidence interval max]. Reported confidence interval for this test is " << confidence << TestLog::EndMessage;
		log << TestLog::Message << "Render time for scene with depth test was\n\t"
			<< "[" << testParam.offsetConfidenceLower << ", " << testParam.offset <<  ", " << testParam.offsetConfidenceUpper << "]us +"
			<< "[" << testParam.coefficientConfidenceLower << ", " << testParam.coefficient << ", " << testParam.coefficientConfidenceUpper << "]"
			<< "us/workload" << TestLog::EndMessage;
		log << TestLog::EndSection;

		log << TestLog::Section("Result", "Result");

		if (testParam.coefficientConfidenceLower < 0.0f)
		{
			log << TestLog::Message << "Coefficient confidence bounds include values below 0.0, the operation likely has neglible per-pixel cost" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, "Pass");
		}
		else if (testParam.coefficientConfidenceLower < testParam.coefficientConfidenceUpper*0.25)
		{
			log << TestLog::Message << "Coefficient confidence range is extremely large, cannot give reliable result" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, "Result confidence extremely low");
		}
		else
		{
			log << TestLog::Message << "Culled hidden pixels @ " << mpxPerS << "Mpx/s" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, de::floatToString(mpxPerS, 2));
		}

		log << TestLog::EndSection;
	}
}

Sample RenderCountCase::renderSample (const RenderData& occluder, const RenderData& occluded, int callcount) const
{
	const glw::Functions&	gl		= m_renderCtx.getFunctions();
	Sample					sample;
	deUint64				now		= 0;
	deUint64				prev	= 0;
	deUint8					buffer[4];

	// Stabilize
	{
		gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		gl.enable(GL_DEPTH_TEST);
		gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	}

	prev = deGetMicroseconds();

	gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	gl.enable(GL_DEPTH_TEST);

	render(occluder);
	render(occluded, callcount);

	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

	now = deGetMicroseconds();

	sample.testTime = now - prev;
	sample.baseTime = 0;
	sample.nullTime = 0;
	sample.workload = callcount;

	return sample;
}

int RenderCountCase::calibrate (void) const
{
	using namespace gls;

	const glw::Functions&	gl					= m_renderCtx.getFunctions();
	TestLog&				log					= m_testCtx.getLog();

	const RenderData		occluderGeometry	(genOccluderGeometry(), m_renderCtx, log);
	const RenderData		occludedGeometry	(genOccludedGeometry(), m_renderCtx, log);

	TheilSenCalibrator		calibrator			(CalibratorParameters(20, // Initial workload
																	  10, // Max iteration frames
																	  20.0f, // Iteration shortcut threshold ms
																	  20, // Max iterations
																	  33.0f, // Target frame time
																	  40.0f, // Frame time cap
																	  1000.0f // Target measurement duration
																	  ));

	while (true)
	{
		switch(calibrator.getState())
		{
			case TheilSenCalibrator::STATE_FINISHED:
				logCalibrationInfo(m_testCtx.getLog(), calibrator);
				return calibrator.getCallCount();

			case TheilSenCalibrator::STATE_MEASURE:
			{
				deUint8	buffer[4];
				deInt64 now;
				deInt64 prev;

				prev = deGetMicroseconds();

				gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
				gl.disable(GL_DEPTH_TEST);

				render(occluderGeometry);
				render(occludedGeometry, calibrator.getCallCount());

				gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

				now = deGetMicroseconds();

				calibrator.recordIteration(now - prev);
				break;
			}

			case TheilSenCalibrator::STATE_RECOMPUTE_PARAMS:
				calibrator.recomputeParameters();
				break;
			default:
				DE_ASSERT(false);
				return 1;
		}
	}
}

// Compares time/workload gradients of same geometry with and without depth testing
class RelativeChangeCase : public BaseCase
{
public:
					RelativeChangeCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc);
	virtual			~RelativeChangeCase	(void) {}

protected:
	Sample			renderSample		(const RenderData& occluder, const RenderData& occluded, int workload) const;

	virtual void	logAnalysis			(const vector<Sample>& samples);

private:
	int				calibrate			(void) const;
};

RelativeChangeCase::RelativeChangeCase (TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
	: BaseCase		(testCtx, renderCtx, name, desc)
{
}

int RelativeChangeCase::calibrate (void) const
{
	using namespace gls;

	const glw::Functions&	gl		= m_renderCtx.getFunctions();
	TestLog&				log		= m_testCtx.getLog();

	const RenderData		geom	(genOccludedGeometry(), m_renderCtx, log);

	TheilSenCalibrator calibrator(CalibratorParameters( 20, // Initial workload
														10, // Max iteration frames
														20.0f, // Iteration shortcut threshold ms
														20, // Max iterations
														33.0f, // Target frame time
														40.0f, // Frame time cap
														1000.0f // Target measurement duration
														));

	while (true)
	{
		switch(calibrator.getState())
		{
			case TheilSenCalibrator::STATE_FINISHED:
				logCalibrationInfo(m_testCtx.getLog(), calibrator);
				return calibrator.getCallCount();

			case TheilSenCalibrator::STATE_MEASURE:
			{
				deUint8			buffer[4];
				const GLuint	program	= geom.m_program.getProgram();

				gl.useProgram(program);
				gl.uniform1i(gl.getUniformLocation(program, "u_iterations"), calibrator.getCallCount());

				const deInt64 prev = deGetMicroseconds();

				gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
				gl.disable(GL_DEPTH_TEST);

				render(geom);

				gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

				const deInt64 now = deGetMicroseconds();

				calibrator.recordIteration(now - prev);
				break;
			}

			case TheilSenCalibrator::STATE_RECOMPUTE_PARAMS:
				calibrator.recomputeParameters();
				break;
			default:
				DE_ASSERT(false);
				return 1;
		}
	}
}

Sample RelativeChangeCase::renderSample (const RenderData& occluder, const RenderData& occluded, int workload) const
{
	const glw::Functions&	gl		= m_renderCtx.getFunctions();
	const GLuint			program	= occluded.m_program.getProgram();
	Sample					sample;
	deUint64				now		= 0;
	deUint64				prev	= 0;
	deUint8					buffer[4];

	gl.useProgram(program);
	gl.uniform1i(gl.getUniformLocation(program, "u_iterations"), workload);

	// Warmup (this workload seems to reduce variation in following workloads)
	{
		gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		gl.disable(GL_DEPTH_TEST);

		gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	}

	// Null time
	{
		prev = deGetMicroseconds();

		gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		gl.disable(GL_DEPTH_TEST);

		gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

		now = deGetMicroseconds();

		sample.nullTime = now - prev;
	}

	// Test time
	{
		prev = deGetMicroseconds();

		gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		gl.enable(GL_DEPTH_TEST);

		render(occluder);
		render(occluded);

		gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

		now = deGetMicroseconds();

		sample.testTime = now - prev;
	}

	// Base time
	{
		prev = deGetMicroseconds();

		gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		gl.disable(GL_DEPTH_TEST);

		render(occluder);
		render(occluded);

		gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

		now = deGetMicroseconds();

		sample.baseTime = now - prev;
	}

	sample.workload = 0;

	return sample;
}

void RelativeChangeCase::logAnalysis (const vector<Sample>& samples)
{
	using namespace gls;

	TestLog&		log			= m_testCtx.getLog();

	int				maxWorkload	= 0;

	vector<Vec2>	nullSamples	(samples.size());
	vector<Vec2>	baseSamples	(samples.size());
	vector<Vec2>	testSamples	(samples.size());

	for (size_t ndx = 0; ndx < samples.size(); ndx++)
	{
		const Sample& sample = samples[ndx];

		nullSamples[ndx] = Vec2((float)sample.workload, (float)sample.nullTime);
		baseSamples[ndx] = Vec2((float)sample.workload, (float)sample.baseTime);
		testSamples[ndx] = Vec2((float)sample.workload, (float)sample.testTime);

		maxWorkload = de::max(maxWorkload, sample.workload);
	}

	{
		const float							confidence	= 0.60f;

		const LineParametersWithConfidence	nullParam	= theilSenSiegelLinearRegression(nullSamples, confidence);
		const LineParametersWithConfidence	baseParam	= theilSenSiegelLinearRegression(baseSamples, confidence);
		const LineParametersWithConfidence	testParam	= theilSenSiegelLinearRegression(testSamples, confidence);

		if (!de::inRange(0.0f, nullParam.coefficientConfidenceLower, nullParam.coefficientConfidenceUpper))
		{
			m_results.addResult(QP_TEST_RESULT_FAIL, "Constant operation sequence duration not constant");
			log << TestLog::Message << "Constant operation sequence timing may vary as a function of workload. Result quality extremely low" << TestLog::EndMessage;
		}

		if (de::inRange(0.0f, baseParam.coefficientConfidenceLower, baseParam.coefficientConfidenceUpper))
		{
			m_results.addResult(QP_TEST_RESULT_FAIL, "Workload has no effect on duration");
			log << TestLog::Message << "Workload factor has no effect on duration of sample (smart optimizer?)" << TestLog::EndMessage;
		}

		log << TestLog::Section("Linear Regression", "Linear Regression");
		log << TestLog::Message << "Offset & coefficient presented as [confidence interval min, estimate, confidence interval max]. Reported confidence interval for this test is " << confidence << TestLog::EndMessage;

		log << TestLog::Message << "Render time for empty scene was\n\t"
			<< "[" << nullParam.offsetConfidenceLower << ", " << nullParam.offset <<  ", " << nullParam.offsetConfidenceUpper << "]us +"
			<< "[" << nullParam.coefficientConfidenceLower << ", " << nullParam.coefficient << ", " << nullParam.coefficientConfidenceUpper << "]"
			<< "us/workload" << TestLog::EndMessage;

		log << TestLog::Message << "Render time for scene without depth test was\n\t"
			<< "[" << baseParam.offsetConfidenceLower << ", " << baseParam.offset <<  ", " << baseParam.offsetConfidenceUpper << "]us +"
			<< "[" << baseParam.coefficientConfidenceLower << ", " << baseParam.coefficient << ", " << baseParam.coefficientConfidenceUpper << "]"
			<< "us/workload" << TestLog::EndMessage;

		log << TestLog::Message << "Render time for scene with depth test was\n\t"
			<< "[" << testParam.offsetConfidenceLower << ", " << testParam.offset <<  ", " << testParam.offsetConfidenceUpper << "]us +"
			<< "[" << testParam.coefficientConfidenceLower << ", " << testParam.coefficient << ", " << testParam.coefficientConfidenceUpper << "]"
			<< "us/workload" << TestLog::EndMessage;

		log << TestLog::EndSection;

		if (de::inRange(0.0f, testParam.coefficientConfidenceLower, testParam.coefficientConfidenceUpper))
		{
			log << TestLog::Message << "Test duration not dependent on culled workload" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, "0.0");
		}
		else if (testParam.coefficientConfidenceLower < testParam.coefficientConfidenceUpper*0.25)
		{
			log << TestLog::Message << "Coefficient confidence range is extremely large, cannot give reliable result" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, "Result confidence extremely low");
		}
		else if (baseParam.coefficientConfidenceLower < baseParam.coefficientConfidenceUpper*0.25)
		{
			log << TestLog::Message << "Coefficient confidence range for base render time is extremely large, cannot give reliable result" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, "Result confidence extremely low");
		}
		else
		{
			log << TestLog::Message << "Test duration is dependent on culled workload" << TestLog::EndMessage;
			m_results.addResult(QP_TEST_RESULT_PASS, de::floatToString(de::abs(testParam.coefficient)/de::abs(baseParam.coefficient), 2));
		}
	}
}

// Speed of trivial culling
class BaseCostCase : public RenderCountCase
{
public:
						BaseCostCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RenderCountCase (testCtx, renderCtx, name, desc) {}

						~BaseCostCase		(void) {}

private:
	virtual ObjectData	genOccluderGeometry	(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry	(void) const { return Utils::variableQuad(0.8f); }

	virtual void		logDescription		(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered"  << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Gradient
class GradientCostCase : public RenderCountCase
{
public:
						GradientCostCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc, float gradientDistance)
							: RenderCountCase		(testCtx, renderCtx, name, desc)
							, m_gradientDistance	(gradientDistance)
						{
						}

						~GradientCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry	(void) const { return Utils::fastQuadWithGradient(0.0f, 1.0f - m_gradientDistance); }
	virtual ObjectData	genOccludedGeometry	(void) const
	{
		return ObjectData(glu::makeVtxFragSources(Utils::getInstanceNoiseVertexShader(), Utils::getDepthAsRedFragmentShader()), Utils::getFullscreenQuadWithGradient(m_gradientDistance, 1.0f));
	}

	virtual void		logDescription		(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered" << TestLog::EndMessage;
		log << TestLog::Message << "The quads are tilted so that the left edge of the occluded quad has a depth of 1.0 and the right edge of the occluding quad has a depth of 0.0." << TestLog::EndMessage;
		log << TestLog::Message << "The quads are spaced to have a depth difference of " << m_gradientDistance << " at all points." << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}

	const float			m_gradientDistance;
};

// Constant offset to frag depth in occluder
class OccluderStaticFragDepthCostCase : public RenderCountCase
{
public:
						OccluderStaticFragDepthCostCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RenderCountCase(testCtx, renderCtx, name, desc)
						{
						}

						~OccluderStaticFragDepthCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::quadWith(Utils::getStaticFragDepthFragmentShader(), 0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::fastQuad(0.8f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered" << TestLog::EndMessage;
		log << TestLog::Message << "The occluder quad has a static offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Dynamic offset to frag depth in occluder
class OccluderDynamicFragDepthCostCase : public RenderCountCase
{
public:
						OccluderDynamicFragDepthCostCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RenderCountCase(testCtx, renderCtx, name, desc)
						{
						}

						~OccluderDynamicFragDepthCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::quadWith(Utils::getDynamicFragDepthFragmentShader(), 0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::fastQuad(0.8f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered" << TestLog::EndMessage;
		log << TestLog::Message << "The occluder quad has a dynamic offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Constant offset to frag depth in occluder
class OccludedStaticFragDepthCostCase : public RenderCountCase
{
public:
						OccludedStaticFragDepthCostCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RenderCountCase(testCtx, renderCtx, name, desc)
						{
						}

						~OccludedStaticFragDepthCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::quadWith(Utils::getStaticFragDepthFragmentShader(), 0.2f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered" << TestLog::EndMessage;
		log << TestLog::Message << "The occluded quad has a static offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Dynamic offset to frag depth in occluder
class OccludedDynamicFragDepthCostCase : public RenderCountCase
{
public:
						OccludedDynamicFragDepthCostCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RenderCountCase(testCtx, renderCtx, name, desc)
						{
						}

						~OccludedDynamicFragDepthCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::quadWith(Utils::getDynamicFragDepthFragmentShader(), 0.2f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) is rendered once, the second (occluded) is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered" << TestLog::EndMessage;
		log << TestLog::Message << "The occluded quad has a dynamic offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Culling speed with slightly less trivial geometry
class OccludingGeometryComplexityCostCase : public RenderCountCase
{
public:
						OccludingGeometryComplexityCostCase		(TestContext&			testCtx,
																 const RenderContext&	renderCtx,
																 const char*			name,
																 const char*			desc,
																 int					resolution,
																 float					xyNoise,
																 float					zNoise)
							: RenderCountCase	(testCtx, renderCtx, name, desc)
							, m_resolution		(resolution)
							, m_xyNoise			(xyNoise)
							, m_zNoise			(zNoise)
						{
						}

						~OccludingGeometryComplexityCostCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry						(void) const
	{
		return ObjectData(Utils::getBaseShader(),
						  Utils::getFullScreenGrid(m_resolution,
						  deInt32Hash(deStringHash(getName())) ^ m_testCtx.getCommandLine().getBaseSeed(),
						  0.2f,
						  m_zNoise,
						  m_xyNoise));
	}

	virtual ObjectData	genOccludedGeometry						(void) const { return Utils::variableQuad(0.8f); }

	virtual void		logDescription		(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing hidden fragment culling speed" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of an occluding grid and an occluded fullsceen quad. The occluding geometry is rendered once, the occluded one is rendered repeatedly" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of times the occluded quad is rendered"  << TestLog::EndMessage;
		log << TestLog::Message << "The time per culled pixel is estimated from the rate of change of rendering time as a function of workload"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}

	const int			m_resolution;
	const float			m_xyNoise;
	const float			m_zNoise;
};


// Cases with varying workloads in the fragment shader
class FragmentWorkloadCullCase : public RelativeChangeCase
{
public:
						FragmentWorkloadCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc);
	virtual				~FragmentWorkloadCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry			(void) const { return Utils::fastQuad(0.2f); }

	virtual void		logDescription				(void);
};

FragmentWorkloadCullCase::FragmentWorkloadCullCase (TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
	: RelativeChangeCase	(testCtx, renderCtx, name, desc)
{
}

void FragmentWorkloadCullCase::logDescription (void)
{
	TestLog& log = m_testCtx.getLog();

	log << TestLog::Section("Description", "Test description");
	log << TestLog::Message << "Testing effects of culled fragment workload on render time" << TestLog::EndMessage;
	log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) quad uses a trivial shader,"
		"the second (occluded) contains significant fragment shader work" << TestLog::EndMessage;
	log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
	log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
	log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
	log << TestLog::EndSection;
}

// Additional workload consists of texture lookups
class FragmentTextureWorkloadCullCase : public FragmentWorkloadCullCase
{
public:
						FragmentTextureWorkloadCullCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc);
	virtual				~FragmentTextureWorkloadCullCase	(void) {}

	virtual void		init								(void);
	virtual void		deinit								(void);

private:
	typedef MovePtr<glu::Texture> TexPtr;

	virtual ObjectData	genOccludedGeometry					(void) const
	{
		return ObjectData(Utils::getTextureWorkloadShader(), Utils::getFullscreenQuad(0.8f));
	}

	TexPtr				m_texture;
};

FragmentTextureWorkloadCullCase::FragmentTextureWorkloadCullCase (TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
	: FragmentWorkloadCullCase	(testCtx, renderCtx, name, desc)
{
}

void FragmentTextureWorkloadCullCase::init (void)
{
	const glw::Functions&	gl		= m_renderCtx.getFunctions();
	const int				size	= 128;
	const vector<deUint8>	data	(size*size*4, 255);

	m_texture = MovePtr<glu::Texture>(new glu::Texture(gl));

	gl.bindTexture(GL_TEXTURE_2D, m_texture);
	gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, &data[0]);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

void FragmentTextureWorkloadCullCase::deinit (void)
{
	m_texture.clear();
}

// Additional workload consists of arithmetic
class FragmentArithmeticWorkloadCullCase : public FragmentWorkloadCullCase
{
public:
						FragmentArithmeticWorkloadCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
						: FragmentWorkloadCullCase	(testCtx, renderCtx, name, desc)
					{
					}
	virtual				~FragmentArithmeticWorkloadCullCase	(void) {}

private:
	virtual ObjectData	genOccludedGeometry					(void) const
	{
		return ObjectData(Utils::getArithmeticWorkloadShader(), Utils::getFullscreenQuad(0.8f));
	}
};

// Contains dynamicly unused discard after a series of calculations
class FragmentDiscardArithmeticWorkloadCullCase : public FragmentWorkloadCullCase
{
public:
						FragmentDiscardArithmeticWorkloadCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
						: FragmentWorkloadCullCase	(testCtx, renderCtx, name, desc)
					{
					}

	virtual				~FragmentDiscardArithmeticWorkloadCullCase	(void) {}

private:
	virtual ObjectData	genOccludedGeometry							(void) const
	{
		return ObjectData(Utils::getArithmeticWorkloadDiscardShader(), Utils::getFullscreenQuad(0.8f));
	}

	virtual void		logDescription								(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of culled fragment workload on render time" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) quad uses a trivial shader,"
			"the second (occluded) contains significant fragment shader work and a discard that is never triggers but has a dynamic condition" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Discards fragments from the occluder in a grid pattern
class PartialOccluderDiscardCullCase : public RelativeChangeCase
{
public:
						PartialOccluderDiscardCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc, int gridsize)
							: RelativeChangeCase		(testCtx, renderCtx, name, desc)
							, m_gridsize	(gridsize)
						{
						}
	virtual				~PartialOccluderDiscardCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry				(void) const { return Utils::quadWith(Utils::getGridDiscardShader(m_gridsize), 0.2f); }
	virtual ObjectData	genOccludedGeometry				(void) const { return Utils::slowQuad(0.8f); }

	virtual void		logDescription					(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of partially discarded occluder on rendering time" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullsceen quads. The first (occluding) quad discards half the "
			"fragments in a grid pattern, the second (partially occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in depth testing halving the render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}

	const int			m_gridsize;
};

// Trivial occluder covering part of screen
class PartialOccluderCullCase : public RelativeChangeCase
{
public:
						PartialOccluderCullCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc, float coverage)
							: RelativeChangeCase		(testCtx, renderCtx, name, desc)
							, m_coverage	(coverage)
						{
						}
						~PartialOccluderCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry			(void) const { return ObjectData(Utils::getBaseShader(), Utils::getPartScreenQuad(m_coverage, 0.2f)); }
	virtual ObjectData	genOccludedGeometry			(void) const {return Utils::slowQuad(0.8f); }

	virtual void		logDescription				(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of partial occluder on rendering time" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two quads. The first (occluding) quad covers " << m_coverage*100.0f
			<< "% of the screen, while the second (partially occluded, fullscreen) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in render time increasing proportionally with unoccluded area"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}

	const float			m_coverage;
};

// Constant offset to frag depth in occluder
class StaticOccluderFragDepthCullCase : public RelativeChangeCase
{
public:
						StaticOccluderFragDepthCullCase		(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RelativeChangeCase(testCtx, renderCtx, name, desc)
						{
						}

						~StaticOccluderFragDepthCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::quadWith(Utils::getStaticFragDepthFragmentShader(), 0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::slowQuad(0.8f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of non-default frag depth on culling efficiency" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullscreen quads. The first (occluding) quad is trivial, while the second (occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The occluder quad has a static offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Dynamic offset to frag depth in occluder
class DynamicOccluderFragDepthCullCase : public RelativeChangeCase
{
public:
						DynamicOccluderFragDepthCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RelativeChangeCase(testCtx, renderCtx, name, desc)
						{
						}

						~DynamicOccluderFragDepthCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::quadWith(Utils::getDynamicFragDepthFragmentShader(), 0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::slowQuad(0.8f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of non-default frag depth on culling efficiency" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullscreen quads. The first (occluding) quad is trivial, while the second (occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The occluder quad has a dynamic offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Constant offset to frag depth in occluded
class StaticOccludedFragDepthCullCase : public RelativeChangeCase
{
public:
						StaticOccludedFragDepthCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RelativeChangeCase(testCtx, renderCtx, name, desc)
						{
						}

						~StaticOccludedFragDepthCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::quadWith(Utils::getStaticFragDepthArithmeticWorkloadFragmentShader(), 0.2f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of non-default frag depth on rendering time" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullscreen quads. The first (occluding) quad is trivial, while the second (occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The occluded quad has a static offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Dynamic offset to frag depth in occluded
class DynamicOccludedFragDepthCullCase : public RelativeChangeCase
{
public:
						DynamicOccludedFragDepthCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RelativeChangeCase(testCtx, renderCtx, name, desc)
						{
						}

						~DynamicOccludedFragDepthCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry					(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry					(void) const { return Utils::quadWith(Utils::getDynamicFragDepthArithmeticWorkloadFragmentShader(), 0.2f); }

	virtual void		logDescription						(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of non-default frag depth on rendering time" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullscreen quads. The first (occluding) quad is trivial, while the second (occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The occluded quad has a dynamic offset applied to gl_FragDepth" << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}
};

// Dynamic offset to frag depth in occluded
class ReversedDepthOrderCullCase : public RelativeChangeCase
{
public:
						ReversedDepthOrderCullCase	(TestContext& testCtx, const RenderContext& renderCtx, const char* name, const char* desc)
							: RelativeChangeCase(testCtx, renderCtx, name, desc)
						{
						}

						~ReversedDepthOrderCullCase	(void) {}

private:
	virtual ObjectData	genOccluderGeometry			(void) const { return Utils::fastQuad(0.2f); }
	virtual ObjectData	genOccludedGeometry			(void) const { return Utils::slowQuad(0.8f); }

	virtual void		logDescription				(void)
	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Section("Description", "Test description");
		log << TestLog::Message << "Testing effects of of back first rendering order on culling efficiency" << TestLog::EndMessage;
		log << TestLog::Message << "Geometry consists of two fullscreen quads. The second (occluding) quad is trivial, while the first (occluded) contains significant fragment shader work" << TestLog::EndMessage;
		log << TestLog::Message << "Workload indicates the number of iterations of dummy work done in the occluded quad's fragment shader"  << TestLog::EndMessage;
		log << TestLog::Message << "The ratio of rendering times of this scene with/without depth testing are compared"  << TestLog::EndMessage;
		log << TestLog::Message << "Successfull early Z-testing should result in no correlation between workload and render time"  << TestLog::EndMessage;
		log << TestLog::EndSection;
	}

	// Rendering order of occluder & occluded is reversed, otherwise identical to parent version
	Sample				renderSample				(const RenderData& occluder, const RenderData& occluded, int workload) const
	{
		const glw::Functions&	gl		= m_renderCtx.getFunctions();
		const GLuint			program	= occluded.m_program.getProgram();
		Sample					sample;
		deUint64				now		= 0;
		deUint64				prev	= 0;
		deUint8					buffer[4];

		gl.useProgram(program);
		gl.uniform1i(gl.getUniformLocation(program, "u_iterations"), workload);

		// Warmup (this workload seems to reduce variation in following workloads)
		{
			gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			gl.disable(GL_DEPTH_TEST);

			gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
		}

		// Null time
		{
			prev = deGetMicroseconds();

			gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			gl.disable(GL_DEPTH_TEST);

			gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

			now = deGetMicroseconds();

			sample.nullTime = now - prev;
		}

		// Test time
		{
			prev = deGetMicroseconds();

			gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			gl.enable(GL_DEPTH_TEST);

			render(occluded);
			render(occluder);

			gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

			now = deGetMicroseconds();

			sample.testTime = now - prev;
		}

		// Base time
		{
			prev = deGetMicroseconds();

			gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			gl.disable(GL_DEPTH_TEST);

			render(occluded);
			render(occluder);

			gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

			now = deGetMicroseconds();

			sample.baseTime = now - prev;
		}

		sample.workload = 0;

		return sample;
	}
};

} // Anonymous

DepthTests::DepthTests (Context& context)
	: TestCaseGroup (context, "depth", "Depth culling performance")
{
}

void DepthTests::init (void)
{
	TestContext&			testCtx		= m_context.getTestContext();
	const RenderContext&	renderCtx	= m_context.getRenderContext();

	{
		tcu::TestCaseGroup* const cullEfficiencyGroup = new tcu::TestCaseGroup(m_testCtx, "cull_efficiency", "Fragment cull efficiency");

		addChild(cullEfficiencyGroup);

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "workload", "Workload");

			cullEfficiencyGroup->addChild(group);

			group->addChild(new FragmentTextureWorkloadCullCase(			testCtx, renderCtx, "workload_texture",				"Fragment shader with texture lookup workload"));
			group->addChild(new FragmentArithmeticWorkloadCullCase(			testCtx, renderCtx, "workload_arithmetic",			"Fragment shader with arithmetic workload"));
			group->addChild(new FragmentDiscardArithmeticWorkloadCullCase(	testCtx, renderCtx, "workload_arithmetic_discard",	"Fragment shader that may discard with arithmetic workload"));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "occluder_discard", "Discard");

			cullEfficiencyGroup->addChild(group);

			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_256",	"Parts of occluder geometry discarded", 256));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_128",	"Parts of occluder geometry discarded", 128));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_64",	"Parts of occluder geometry discarded", 64));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_32",	"Parts of occluder geometry discarded", 32));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_16",	"Parts of occluder geometry discarded", 16));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_8",	"Parts of occluder geometry discarded", 8));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_4",	"Parts of occluder geometry discarded", 4));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_2",	"Parts of occluder geometry discarded", 2));
			group->addChild(new PartialOccluderDiscardCullCase(testCtx, renderCtx, "grid_1",	"Parts of occluder geometry discarded", 1));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "partial_coverage", "Partial Coverage");

			cullEfficiencyGroup->addChild(group);

			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "100", "Occluder covering only part of occluded geometry", 1.00f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "099", "Occluder covering only part of occluded geometry", 0.99f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "095", "Occluder covering only part of occluded geometry", 0.95f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "090", "Occluder covering only part of occluded geometry", 0.90f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "080", "Occluder covering only part of occluded geometry", 0.80f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "070", "Occluder covering only part of occluded geometry", 0.70f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "050", "Occluder covering only part of occluded geometry", 0.50f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "025", "Occluder covering only part of occluded geometry", 0.25f));
			group->addChild(new PartialOccluderCullCase(testCtx, renderCtx, "010", "Occluder covering only part of occluded geometry", 0.10f));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "frag_depth", "Partial Coverage");

			cullEfficiencyGroup->addChild(group);

			group->addChild(new StaticOccluderFragDepthCullCase( testCtx, renderCtx, "occluder_static", ""));
			group->addChild(new DynamicOccluderFragDepthCullCase(testCtx, renderCtx, "occluder_dynamic", ""));
			group->addChild(new StaticOccludedFragDepthCullCase( testCtx, renderCtx, "occluded_static", ""));
			group->addChild(new DynamicOccludedFragDepthCullCase(testCtx, renderCtx, "occluded_dynamic", ""));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "order", "Rendering order");

			cullEfficiencyGroup->addChild(group);

			group->addChild(new ReversedDepthOrderCullCase(testCtx, renderCtx, "reversed", "Back to front rendering order"));
		}
	}

	{
		tcu::TestCaseGroup* const testCostGroup = new tcu::TestCaseGroup(m_testCtx, "culled_pixel_cost", "Fragment cull efficiency");

		addChild(testCostGroup);

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "gradient", "Gradients with small depth differences");

			testCostGroup->addChild(group);

			group->addChild(new BaseCostCase(testCtx, renderCtx, "flat", ""));
			group->addChild(new GradientCostCase(testCtx, renderCtx, "gradient_050", "", 0.50f));
			group->addChild(new GradientCostCase(testCtx, renderCtx, "gradient_010", "", 0.10f));
			group->addChild(new GradientCostCase(testCtx, renderCtx, "gradient_005", "", 0.05f));
			group->addChild(new GradientCostCase(testCtx, renderCtx, "gradient_002", "", 0.02f));
			group->addChild(new GradientCostCase(testCtx, renderCtx, "gradient_001", "", 0.01f));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "occluder_geometry", "Occluders with varying geometry complexity");

			testCostGroup->addChild(group);

			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_uniform_grid_5",   "", 5,   0.0f, 0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_uniform_grid_15",  "", 15,  0.0f, 0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_uniform_grid_25",  "", 25,  0.0f, 0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_uniform_grid_50",  "", 50,  0.0f, 0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_uniform_grid_100", "", 100, 0.0f, 0.0f));

			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_noisy_grid_5",   "", 5,   1.0f/5.0f,   0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_noisy_grid_15",  "", 15,  1.0f/15.0f,  0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_noisy_grid_25",  "", 25,  1.0f/25.0f,  0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_noisy_grid_50",  "", 50,  1.0f/50.0f,  0.0f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "flat_noisy_grid_100", "", 100, 1.0f/100.0f, 0.0f));

			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_uniform_grid_5",   "", 5,   0.0f, 0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_uniform_grid_15",  "", 15,  0.0f, 0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_uniform_grid_25",  "", 25,  0.0f, 0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_uniform_grid_50",  "", 50,  0.0f, 0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_uniform_grid_100", "", 100, 0.0f, 0.2f));

			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_noisy_grid_5",   "", 5,   1.0f/5.0f,   0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_noisy_grid_15",  "", 15,  1.0f/15.0f,  0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_noisy_grid_25",  "", 25,  1.0f/25.0f,  0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_noisy_grid_50",  "", 50,  1.0f/50.0f,  0.2f));
			group->addChild(new OccludingGeometryComplexityCostCase(testCtx, renderCtx, "uneven_noisy_grid_100", "", 100, 1.0f/100.0f, 0.2f));
		}

		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "frag_depth", "Modifying gl_FragDepth");

			testCostGroup->addChild(group);

			group->addChild(new OccluderStaticFragDepthCostCase( testCtx, renderCtx, "occluder_static", ""));
			group->addChild(new OccluderDynamicFragDepthCostCase(testCtx, renderCtx, "occluder_dynamic", ""));
			group->addChild(new OccludedStaticFragDepthCostCase( testCtx, renderCtx, "occluded_static", ""));
			group->addChild(new OccludedDynamicFragDepthCostCase(testCtx, renderCtx, "occluded_dynamic", ""));
		}
	}
}

} // Performance
} // gles3
} // deqp
